//////////////////////////////////////////////////////////////////////////
//
// This file is a part of XORT project.
//
// PROJECT:         Xort compiler
// FILE:            main.cpp
// AUTHOR:          Vladimir Gumenuk
// DATE:            20-Mar-2008
// MODIFICATIONS:
//
//
//     Copyright(c) 2014, Vladimir Gumenuk
//     All rights reserved.
//
//     Redistribution and use in source and binary forms, with or without modification, 
//     are permitted provided that the following conditions are met :
//
//     1. Redistributions of source code must retain the above copyright notice, 
//        this list of conditions and the following disclaimer.
//     2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and 
//        the following disclaimer in the documentation and / or other materials provided with the distribution.
//     3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse 
//        or promote products derived from this software without specific prior written permission.
//
//     THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
//     ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 
//     OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 
//     THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
//     SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT 
//     OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
//     HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 
//     OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 
//     EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
//////////////////////////////////////////////////////////////////////////

#ifdef CPP_COMPILER
#include "xort.h"
#include <windows.h>
#else
#include "xort.h"
#include "xprim.cpp"
#include "lex.cpp"
#include "expr.cpp"
#include "syntax.cpp"
#include "SymTab.cpp"
#include "utils.cpp"
#include "dasm.cpp"
#include "io.cpp"
#include "codegen.cpp"
#include "dumper.cpp"
#include "optmizer.cpp"
#endif

string CreateOutputAsmFileName( string csIn )
{
    string csOut = csIn;
    int iPos = csOut.ReverseFind( '.' );
    if( iPos != -1 ) {
        csOut.Truncate( iPos );
        csOut.Add( ".asm" );
    } else {
        csOut.Replace( ".", "_", 1 );
        csOut.Add( ".asm" );
    }

    DbgPrint( "ASM listing file created at: %@", csOut );
    return csOut;
}

string CreateOutputExeFileName( string csIn )
{
    string csOut = csIn;
    int iPos = csOut.ReverseFind( '.' );
    if( iPos != -1 ) {
        csOut.Truncate( iPos );
        csOut.Add( ".xrt" );
    } else {
        csOut.Replace( ".", "_", 1 );
        csOut.Add( ".xrt" );
    }

    DbgPrint( "XORT executable file created at: %@", csOut );
    return csOut;
}

int compile( string csFile, SYMBOLLIST listGlobal )
{
    int iRet = lex_Input( csFile );
    if( iRet == XORT_OK )
    {
        dword dwFlags = 0;
        Token Tok;
        DbgPrint( "compile() -> ENTER" );

        do
        {
            ResetLocalIdx();
            ResetArgIdx();
            ResetStackVarIdx();

            Tok = lex_GetToken( listGlobal );
            DbgPrint( "processing high-level token: %@", Tok.m_csVal );

            if( Tok.m_TT == tt_eof ) {
                break;
            }
            else if( Tok.m_TT == tt_delim && Tok.m_dwTokenCode == tk_sqrbropen ) // attributes []
            {
                ATTR_ARRAY attrArray;
                iRet = do_attributes( listGlobal, attrArray );
                if( iRet == XORT_OK )
                { // expected function declaration or object declaration
                    
                    Tok = lex_GetToken( listGlobal );
                    if( Tok.m_TT == tt_typename )
                    {
                        Symbol nullSym; nullSym.Zero();
                        string csProcName;
                        iRet = do_var_proc( listGlobal, Tok.m_dwTokenCode, nullSym, f_proc_expected, csProcName );
                        if( iRet == XORT_OK ) {
                            SymbolRef proc = FindSymbol( listGlobal, csProcName, FALSE );
                            if( proc.m_SymKind != sk_proc ) {
                                Tok = lex_GetLastToken();
                                x_error( ERR_FUNC_DECL_ERROR, Tok.m_dwLine, Tok.m_dwPos );
                                break;
                            }
                            attrArray.copy( proc.m_arAttributes ); // attach attributes to the procedure
                            proc.m_dwFlags |= f_import_proc;
                        }
                    }
                    else if( Tok.m_TT == tt_stmt && Tok.m_dwTokenCode == tk_object )
                    {
                        iRet = validate_obj_attributes( attrArray ); // now we know, that this attributes belong to object
                        if( iRet != XORT_OK )
                            break; // error already reported 

                        string csNameIgnored; // ignored for now...
                        iRet = do_object( listGlobal, attrArray, csNameIgnored );
                    }
                    else
                    {
                        x_error( ERR_UNEXPECTED_TOKEN, Tok.m_dwLine, Tok.m_dwPos );
                        break;
                    }
                    dwFlags = 0;
                }
            }
            else if( Tok.m_TT == tt_modifier ) // const
            {
                if( Tok.m_dwTokenCode == tk_const ) {
                    dwFlags = f_const;
                }
            }
            else if( Tok.m_TT == tt_typename ) // variable declaration or procedure
            {
                Symbol nullSym; nullSym.Zero();
                string csUnused;
                iRet = do_var_proc( listGlobal, Tok.m_dwTokenCode, nullSym, dwFlags, csUnused );
                dwFlags = 0;
            }
            else if( Tok.m_TT == tt_stmt )
            {
                if( dwFlags != 0 ) {
                    x_error( ERR_MODOFIER_APPLIED_SYNTAX, Tok.m_dwLine, Tok.m_dwPos );
                    iRet = ERR_SYNTAX;
                    break;
                }

                if( Tok.m_dwTokenCode == tk_typedef ) // typedef allowed only at global level
                {
                    iRet = do_typedef( listGlobal );
                } else  if( Tok.m_dwTokenCode == tk_object ) { // struct definition
                    string csNameOut; // ignored for now.
                    ATTR_ARRAY attr; // ignored for now
                    iRet = do_object( listGlobal, attr, csNameOut );
                } else if( Tok.m_dwTokenCode == tk_enum ) { // enum declaration
                    iRet = do_enum( listGlobal );
                }
            } else if( Tok.m_TT == tt_ident && Tok.m_dwTokenCode == tk_unknown ) {
                x_error( ERR_UNKNOWN_TOKEN, Tok.m_dwLine, Tok.m_dwPos );
                break;
            } else {
                x_error( ERR_UNEXPECTED_TOKEN, Tok.m_dwLine, Tok.m_dwPos );
                break;
            }
        } while( Tok.m_TT != tt_eof && iRet == XORT_OK );
    }

    if( iRet == XORT_OK )
    {
        if( GetCompilationFlags() & XCL_OPTIMIZATION_ON )
        {
            // 1.
            RemoveRedundandStackTransitionSymbols( listGlobal );

            // 2.
            iRet = optimize_program( listGlobal );

        }
    }

    DbgPrint( "compile() -> LEAVE: iRet: %@", iRet );
    return iRet;
}

// main compiler function
#ifdef CPP_COMPILER

qword GetTimeMs64()
{
    FILETIME ft;
    LARGE_INTEGER li;

    // Get the amount of 100 nano seconds intervals elapsed since January 1, 1601 (UTC)
    GetSystemTimeAsFileTime( &ft );
    li.LowPart = ft.dwLowDateTime;
    li.HighPart = ft.dwHighDateTime;

    qword ret = li.QuadPart;
    ret -= 116444736000000000LL; // Convert from file time to UNIX epoch time.
    ret /= 10000; // From 100 nano seconds (10^-7) to 1 millisecond (10^-3) intervals
    return ret;
}

void main( int argc, char *argv[] )
{
    qword qwStartTime = GetTimeMs64();

    DbgPrint( "Initializing literal pool..." );
    lit_Init();
    printf( "%s\n", lit_Get( 0 ).GetBuffer() );
    printf( "%s\n", lit_Get( 1 ).GetBuffer() );

    if( argc < 2 )
    {
        message( "usage: -X=xort_file [-O=output_directory -S=1]" );
        return;
    }

    string csInputFile;
    string csOutDir;
    string csOutFile;

    char szDir[_MAX_PATH];
    _getcwd( szDir, _MAX_PATH );

    dword dwClFlags = 0;

    for( int i = 1; i < argc; i++ )
    {
        char *p = argv[i];

        if( *p == '/' || *p == '-' )
        {
            p++;

            switch( *p )
            {
            case 'X':
                p += 2;
                if( ! string::strstr( p, szDir ) ) {
                    csInputFile = szDir;
                    csInputFile += "\\";
                    csInputFile += p;
                } else {
                    csInputFile.Assign( p );
                }
                break;

            case 'O':
                p += 2;
                csOutDir.Assign( p );
                break;

            case 'S':
                p += 2;
                dwClFlags |= atoi( p );
                break;

            default:
                printf( "unrecognized command line argument: '%c' - skipped\n", *p );
                break;
            }
        }
    }

    SetCompilationFlags( dwClFlags );

#else // ---------------------------------- XORT CL

void main( STRINGARRAY arArguments )
{
    DbgPrint( "Initializing literal pool..." );
    lit_Init();
    printf( "%s\n", lit_Get( 0 ) );
    printf( "%s\n", lit_Get( 1 ) );

    if( arArguments.count() < 2 )
    {
        message( "usage: -X=xort_file [-O=output_directory] -S=1 -V=1" );
        return;
    }

    string csInputFile;

    dword dwNumArgs = arArguments.count();
    dword i;
    for( i = 0; i < dwNumArgs; ++i )
    {
        string csArg = arArguments.get( i );
        byte b1 = csArg.CharAt( 0 );
        byte b2 = csArg.CharAt( 1 );
        byte b3 = csArg.CharAt( 2 );

        if( ( b1 == '-' || b1 == '/' ) && b3 == '=' ) {
            if( b2 == 'X' ) {
                csInputFile.Assign( csArg );
                csInputFile.Replace( "-X=", "", 1 );
            } else if( b2 == 'S' ) {
                SetCompilationFlags( XCL_OPTIMIZATION_ON );
            } else if( b2 == 'V' ) {
                SetVerbose( TRUE );
                OutMsg( "Set Verbose mode On" );
            }
        }
    }

    string csOutDir;
    string csOutFile;
#endif

    if( !csOutDir.IsEmpty() ) {
        csOutFile.Assign( csOutDir );

        int iPos = csInputFile.ReverseFind( '\\' );
        if( iPos != -1 ) {
            string csFileName = csInputFile.Substring( iPos+1 );

            iPos = csOutFile.ReverseFind( '\\' );
            if( iPos != -1 && ( (iPos+1) != csOutFile.Length() ) )
                csOutFile.Append( '\\' );

            csOutFile.Add( csFileName );
        }
    } else
        csOutFile.Assign( csInputFile );

    io_logOpen( CreateOutputAsmFileName( csOutFile ) );
    SYMBOLLIST listGlobal = GetGlobalSymbolList();
    
    OutMsg( "Initializing symbol table..." );
    InitSymbolTable( listGlobal );

    lit_Add( csInputFile ); // so for now: indexes 0, 1 - compiler info, 2 - full source file path! 3... user strings
    
    OutMsg( "Initializing error table..." );
    InitErrorTable();

    OutMsg( "Initializing primitives table..." );
    xInitPrims();

    OutMsg( "Compiling: %@...", csInputFile );
    int iRet = compile( csInputFile, listGlobal );
    if( iRet == XORT_OK )
    {
        DbgPrint( "Dumping symbol table..." );
        DumpSymbolTable( listGlobal );
        
        DbgPrint( "Dumping literal pool..." );
        lit_Dump();

        DbgPrint( "Generating executable..." );
        GenExecutable( listGlobal, CreateOutputExeFileName( csOutFile ) );
    }

    OutMsg( "Deleting primitives..." );
    xFreePrims();

    OutMsg( "Closing log..." );
#ifdef CPP_COMPILER
    qword qwEndTime = GetTimeMs64();
    message( "Execution time      : %llu ms", qwEndTime - qwStartTime  );
#endif
    io_logClose();

    OutMsg( "Exit." );
}